

package net.qiqbframework.messaging.annotation;


import net.qiqbframework.common.resolving.AnnotatedResolvedAttributes;
import net.qiqbframework.messaging.Message;
import net.qiqbframework.common.resolving.ResolvedAttributes;
import net.qiqbframework.common.utils.AnnotationUtil;
import net.qiqbframework.common.utils.ReflectionUtil;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.Nonnull;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * 通用注解消息处理
 */
@Slf4j
public class AnnotatedMessageExecutableHandler implements MessageExecutableHandler {

    private final Class<?> payloadType;
    /**
     * 参数个数
     */
    private final int parameterCount;

    private final HandlerParameterResolver<?>[] parameterResolvers;
    /**
     * 具体执行返回或构造器
     */
    private final Executable executable;
    /**
     * 消息类型
     */
    private final Class<? extends Message<?>> messageType;
    /**
     * 额外属性
     */
    private final ResolvedAttributes attributes;

    /**
     * Initializes a new instance that will invoke the given {@code executable} (method) on a target to handle a message
     * of the given {@code messageType}.
     *
     * @param executable               the method to invoke on a target
     * @param messageType              the type of message that is expected by the target method
     * @param explicitPayloadType      the expected message payload type
     * @param parameterResolverFactory factory used to resolve method parameters
     */
    public AnnotatedMessageExecutableHandler(Executable executable,
                                             @SuppressWarnings("rawtypes") Class<? extends Message> messageType,
                                             Class<?> explicitPayloadType,
                                             HandlerParameterResolverFactory parameterResolverFactory,
                                             HandlerAttributesPostProcessor handlerAttributesPostProcessor
    ) {
        this.executable = executable;
        // noinspection unchecked
        this.messageType = (Class<? extends Message<?>>) messageType;
        ReflectionUtil.ensureAccessible(this.executable);
        Parameter[] parameters = executable.getParameters();
        this.parameterCount = executable.getParameterCount();
        parameterResolvers = new HandlerParameterResolver[parameterCount];
        Class<?> supportedPayloadType = explicitPayloadType;
        for (int i = 0; i < parameterCount; i++) {
            parameterResolvers[i] = parameterResolverFactory.createInstance(executable, parameters, i);
            if (parameterResolvers[i] == null) {
                throw new UnsupportedHandlerException(
                        "Unable to resolve parameter " + i + " (" + parameters[i].getType().getSimpleName() +
                                ") in handler " + executable.toGenericString() + ".", executable);
            }
            if (supportedPayloadType.isAssignableFrom(parameterResolvers[i].supportedPayloadType())) {
                supportedPayloadType = parameterResolvers[i].supportedPayloadType();
            } else if (!parameterResolvers[i].supportedPayloadType().isAssignableFrom(supportedPayloadType)) {
                throw new UnsupportedHandlerException(String.format(
                        "The method %s seems to have parameters that put conflicting requirements on the payload type" +
                                " applicable on that method: %s vs %s", executable.toGenericString(),
                        supportedPayloadType, parameterResolvers[i].supportedPayloadType()), executable);
            }
        }
        this.payloadType = supportedPayloadType;
        this.attributes = handlerAttributesPostProcessor.postProcess(executable, new AnnotatedResolvedAttributes(executable));

    }

    @Override
    public Class<?> payloadType() {
        return payloadType;
    }

    @Override
    public boolean canHandle(@Nonnull Message<?> message) {
        return typeMatches(message)
                && payloadType.isAssignableFrom(message.getPayloadType())
                && parametersMatch(message);
    }




    @Override
    public Optional<Map<String, Object>> annotationAttributes(Class<? extends Annotation> annotationType) {
        return AnnotationUtil.findAnnotationAttributes(executable, annotationType);
    }

    protected boolean typeMatches(Message<?> message) {
        return messageType.isInstance(message);
    }


    protected boolean parametersMatch(Message<?> message) {
        for (HandlerParameterResolver<?> resolver : parameterResolvers) {
            if (!resolver.support(message)) {
                log.debug("Parameter Resolver [{}] did not match message [{}] for payload type [{}].",
                        resolver.getClass(), message, message.getPayloadType());
                return false;
            }
        }
        return true;
    }

    @Override
    public Object handle(@Nonnull Message<?> message, Object target) throws Exception {
        try {
            if (executable instanceof Method) {
                return ((Method) executable).invoke(target, resolveParameterValues(message));
            } else if (executable instanceof Constructor) {
                return ((Constructor<?>) executable).newInstance(resolveParameterValues(message));
            } else {
                throw new IllegalStateException("What kind of handler is this?");
            }
        } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
            checkAndRethrowForExceptionOrError(e);
            throw new MessageHandlerInvocationException(
                    String.format("Error handling an object of type [%s]", message.getPayloadType()), e);
        }
    }

    private void checkAndRethrowForExceptionOrError(ReflectiveOperationException e) throws Exception {
        if (e.getCause() instanceof Exception) {
            throw (Exception) e.getCause();
        } else if (e.getCause() instanceof Error) {
            throw (Error) e.getCause();
        }
    }

    private Object[] resolveParameterValues(Message<?> message) {
        Object[] params = new Object[parameterCount];
        for (int i = 0; i < parameterCount; i++) {
            params[i] = parameterResolvers[i].resolveParameter(message);
        }
        return params;
    }

    @Override
    public <R> Optional<R> attribute(String attributeKey) {
        return Optional.ofNullable(attributes.get(attributeKey));
    }

    @Override
    public ResolvedAttributes attributes() {
        return attributes;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <H> Optional<H> unwrap(Class<H> handlerType) {
        if (handlerType.isInstance(this)) {
            return (Optional<H>) Optional.of(this);
        }
        if (handlerType.isInstance(executable)) {
            return (Optional<H>) Optional.of(executable);
        }
        return Optional.empty();
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + " " + executable.toGenericString();
    }

    @Override
    public int hashCode() {
        return Objects.hash(executable);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        AnnotatedMessageExecutableHandler that = (AnnotatedMessageExecutableHandler) o;
        return executable.equals(that.executable);
    }
}
